#include "XWindow.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "XWinSystem.h"

#include "Class.h"
#include "String.h"
#include "MemBuf.h"
#include "OrdColl.h"
#include "Error.h"
#include "Data.h"
#include "ClipBoard.h"
#include "System.h"
#include "Env.h"
#include "ET_stdio.h"

#include "XWindowPort.h"
#include "XFont.h"
#include "XBitmap.h"
#include "XIAC.h"
#include "XColorMap.h"

extern bool gWinDebug;

extern "C" int ErrorProc(Display*, XErrorEvent *ev);

EtAtom
	cIsEtWindow             ("ET_IsEtWindow",       eAtomCreate),
	cTakeFocus              ("WM_TAKE_FOCUS",       eAtomCreate),
	cSaveYourself           ("WM_SAVE_YOURSELF",    eAtomCreate),
	cDeleteWindow           ("WM_DELETE_WINDOW",    eAtomCreate),
	cClipboard              ("CLIPBOARD",           eAtomCreate),
	cTargets                ("TARGETS",             eAtomDontCreate),
	cMultiple               ("MULTIPLE",            eAtomDontCreate),
	cTimestamp              ("TIMESTAMP",           eAtomDontCreate),
	cXViewDragLoad          ("XV_DO_DRAG_LOAD",     eAtomDontCreate),
	cOpenLookDecorAdd       ("_OL_DECOR_ADD",       eAtomDontCreate),
	cOpenLookDecorDel       ("_OL_DECOR_DEL",       eAtomDontCreate),
	cOpenLookDecorFooter    ("_OL_DECOR_FOOTER",    eAtomDontCreate),
	cOpenLookWindowBusy     ("_OL_WIN_BUSY",        eAtomDontCreate),
	cOpenLookResize         ("_OL_DECOR_RESIZE",    eAtomDontCreate),
	cOpenLookWinAttr        ("_OL_WIN_ATTR",        eAtomDontCreate),
	cOpenLookWinCmd         ("_OL_WT_CMD",          eAtomDontCreate),
	cOpenLookWinBase        ("_OL_WT_BASE",         eAtomDontCreate),
	cDecWmMoved             ("WM_MOVED",            eAtomDontCreate);

//---- EtAtom ------------------------------------------------------------------

EtAtom::EtAtom(char *nm, XAtomMode m)
{
	name= nm;
	mode= m;
	valid= FALSE;
}

Atom EtAtom::InternAtom()
{
	if (!valid) {
		atom= XInternAtom(XWinSystem::display, name, mode == eAtomDontCreate);
		if (gDebug)
			fprintf(stderr, "InternAtom(%s,%d): %d\n", name, mode, atom);
		valid= TRUE;
	}
	return atom;
}

//---- XDragDropService --------------------------------------------------------

class XDragDropService : public Service {
public:
	XDragDropService() : Service("dragdrop")
		{ }
	int DragDrop(int to, int code, int id, const Point &globalpos, Rectangle *droprect);
	int Dispatch(int from, const char *req, IStream &is, OStream &os);
};

//---- XSelection --------------------------------------------------------------

extern "C" {
	static XBool IsSelectionNotify(XDisplay*, XEvent *xe, char*)
	{
		return (xe->type == SelectionNotify);
	}
	
	static XBool IsPropertyDeleted(XDisplay*, XEvent *xe, char *vp)
	{
		if (xe->type == PropertyNotify) {
			XPropertyEvent *pe= (XPropertyEvent*) xe;
			if (pe->window == *((XWindow*)vp) && pe->atom == cClipboard.XA()
							&& pe->state == PropertyDelete)
				return TRUE;
		}
		return FALSE;
	}
}

//---- XSelection --------------------------------------------------------------

class XSelection : public StreamBufData {
	XWindow awin;
public:
	XSelection(XWindow w);
	StreamBuf *GetStreamBuf(bool forwriting);
};

XSelection::XSelection(XWindow w) : StreamBufData(0, cDocTypeAscii)
{
	awin= w;
}

StreamBuf *XSelection::GetStreamBuf(bool)
{
	if (sb)
		return sb;
		
	XEvent ev;
	XSelectionEvent *se;
	Atom actual_type_return;
	int actual_format_return;
	u_char *prop_return;
	u_long nitems_return, bytes_after_return;

	XConvertSelection(XWinSystem::display, cClipboard.XA(), XA_STRING, None, awin, CurrentTime);
	XIfEvent(XWinSystem::display, &ev, IsSelectionNotify, 0);
	se= (XSelectionEvent*) &ev;

	if (se->property == None) {
		fprintf(stderr, "can't convert selection\n");
		sb= new MemBuf;
		sb->sputn("wenigstens crashed er nicht ...", 31);
		((MemBuf*)sb)->SwitchToRead();
		return sb;
	}

	sb= new MemBuf;
	long offset= 0L;      //  in 32bits quantities == 4 * bytes
	long readsize= 1024;  //  in 32bits quantities == 4 * bytes
	do {
		Status rc= XGetWindowProperty(XWinSystem::display, awin, se->property,
			offset, readsize, XTrue,
			AnyPropertyType, &actual_type_return, &actual_format_return,
				&nitems_return, &bytes_after_return, &prop_return);
		if (rc != Success)
			fprintf(stderr, "can't get WindowProperty\n");
		sb->sputn((char*)prop_return, (int)nitems_return);
		offset+= readsize;
		XFree((char*)prop_return);
	} while (bytes_after_return > 0);

	((MemBuf*)sb)->SwitchToRead();

	return sb;
}

void XWinSystem::DevCheckSelection()
{
/*
	if (xholder == 0)
		xholder= XGetSelectionOwner(display, cClipboard.XA());
	if (xholder != awin) {
		if (gDebug)
			fprintf(stderr, "other x-client has selection\n");
		gClipBoard->SetData(new XSelection(awin));
	}
	*/
}

int XWinSystem::DevHaveSelection(Data *data)
{
	XSetSelectionOwner(display, cClipboard.XA(), awin, GetCurrentServerTime());
	if (XGetSelectionOwner(display, cClipboard.XA()) != awin) {
		xholder= 0;
		fprintf(stderr, "can't become selection owner\n");
	} else {
		xholder= awin;
		/*
		int len;
		char *buf= gClipBoard->GetExtSelection(&len, cDocTypeAscii, 0);
		XStoreBytes(display, buf, len);
		*/
	}
	return WindowSystem::DevHaveSelection(data);
}

void XWinSystem::SendClipBoard(XSelectionRequestEvent *sre)
{
	char *thebuf;
	int thelen= 0;
	XSelectionEvent sev;
	XEvent ev;
	Atom reply;

	sev.type= SelectionNotify;
	sev.display= sre->display;
	sev.selection= sre->selection;
	sev.target= sre->target;
	sev.time= sre->time;
	sev.property= sre->property;
	sev.requestor= sre->requestor;

	if (sev.property == None)
		reply= sev.target;
	else
		reply= sev.property;
		
	if (sev.target == XA_STRING) {
		//thebuf= gClipBoard->GetExtSelection(&thelen, cDocTypeAscii, 0);
		thebuf= "ET++ Clipboard";
		thelen= strlen(thebuf);
		
		XChangeProperty(display, sev.requestor, reply, sev.target, 8,
									PropModeReplace, (byte*) thebuf, thelen);
		XSelectInput(display, sev.requestor, PropertyChangeMask);
		XSendEvent(display, sev.requestor, XFalse, 0, (XEvent*) &sev);
		XFlush(display);
		XCheckIfEvent(display, &ev, IsPropertyDeleted, (char*) &sev.requestor);

	} else if (sev.target == cTargets.XA()) {
		fprintf(stderr, "XClipBoard::SendClipBoard: XA_TARGETS not yet implementes\n");

	} else if (sev.target == cMultiple.XA()) {
		fprintf(stderr, "XClipBoard::SendClipBoard: XA_MULTIPLE not yet implementes\n");

	} else if (sev.target == cTimestamp.XA()) {
		fprintf(stderr, "XClipBoard::SendClipBoard: XA_TIMESTAMP not yet implementes\n");

	} else {
		char *nm= XGetAtomName(XWinSystem::display, sev.target);
		fprintf(stderr, "XClipBoard::SendClipBoard: can't convert to %s\n", nm);
		XFree(nm);
		sev.property= None;
		XSendEvent(display, sev.requestor, XFalse, 0, (XEvent*) &sev);
	}
}

//---- XInputHandler -----------------------------------------------------------

class XInputHandler : public SysEvtHandler {
	XWinSystem *xws;
public:
	XInputHandler(XWinSystem *x, int fd) : SysEvtHandler(fd)
		{ xws= x; }     
	bool Notify()
		{ return xws->Notify(); }
};

//---- XWinSystem --------------------------------------------------------------

NewMetaImpl0(XWinSystem,WindowSystem);

XCursor XWinSystem::CurMap[cMaxCursors];
XDisplay *XWinSystem::display;
XWindow XWinSystem::rootwin;
int XWinSystem::screen;
u_long XWinSystem::black, XWinSystem::white;
Visual *XWinSystem::dfltvisual;
Pixmap XWinSystem::batchPixmap;
bool XWinSystem::allow;
XWindow XWinSystem::busy_winid;

XWinSystem::XWinSystem()
{
}

static struct vclass_t {
	const char *name;
	int code;
} vclass[] = {
#if 0
	{ "TrueColor",    TrueColor },
	{ "DirectColor",  DirectColor },
#endif
	{ "PseudoColor",  PseudoColor },
	{ "StaticColor",  StaticColor },
	{ "GrayScale",    GrayScale },
	{ "StaticGray",   StaticGray },
	{ 0, 0 }
};

extern SysEvtHandler *gXDisplay;

bool XWinSystem::Init0()
{
	if ((display= XOpenDisplay(0)) == 0)
		return FALSE;

	if (gDebug)
		XSynchronize(display, XTrue);

	XSetErrorHandler(ErrorProc);
	
	screen= XDefaultScreen(display);
	rootwin= XRootWindow(display, screen);
	
	black= XBlackPixel(display, screen);
	white= XWhitePixel(display, screen);

	screenRect.extent.x= XDisplayWidth(display, screen);
	screenRect.extent.y= XDisplayHeight(display, screen);
	
	XVisualInfo vinfo, *vis= 0;
	int d, c;

	for (d= gMaxDepth; d > 0; d--) {
		for (c= 0; vclass[c].name; c++) {
			if (XMatchVisualInfo(display, screen, d, vclass[c].code, &vinfo)) {
				vis= &vinfo;
				break;
			}
		}
		if (vis)
			break;
	}

	if (vis == 0) {
		fprintf(stderr, "can't find visual\n");
		return FALSE;
	}

	//if (gDebug)
	fprintf(stderr, "visual: %s(%d) depth: %d\n", vclass[c].name,
						vclass[c].code, vis->depth);

	gDepth= vis->depth;
	dfltvisual= vis->visual;

	switch (vis->c_class) {
	case PseudoColor:
	case StaticColor:
		wcmap= new XColorIndexMapper(XDefaultColormap(display,
					screen), vis->c_class == PseudoColor);
		break;
	case DirectColor:
	case TrueColor:
		wcmap= new ColorDirectMapper(vis->red_mask, vis->green_mask,
					vis->blue_mask, vis->c_class == DirectColor);
		break;
	case GrayScale:
	case StaticGray:
		wcmap= new XColorIndexMapper(XDefaultColormap(display,
					screen), vis->c_class == GrayScale);
		break;
	}

	gXDisplay= inputhandler= new XInputHandler(this, display->fd);
	//gSystem->AddFileHandler(inputhandler);
	bellduration= -1;
	
	xholder= 0;
	awin= XCreateWindow(display, rootwin, 0, 0, 1, 1, 0,
		XDefaultDepth(display, screen), InputOutput, (Visual*) CopyFromParent, 0, 0);
	XSelectInput(display, awin, PropertyChangeMask);
	XSelectInput(display, rootwin, PropertyChangeMask);

	dragDropService= new XDragDropService;

	return TRUE;
}

XWinSystem::~XWinSystem()
{
	SafeDelete(gFontManager);
	SafeDelete(gInkManager);
	SafeDelete(dragDropService);
	SafeDelete(inputhandler);
	gXDisplay= 0;
	
	if (awin)
		XDestroyWindow(display, awin);
	if (batchPixmap) {
		XFreePixmap(display, batchPixmap);
		batchPixmap= 0;
	}
	if (display) {
		XCloseDisplay(display);
		display= 0;
	}
}

int ErrorProc(Display*, XErrorEvent *ev)
{
	if (gWindowSystem)
		((XWinSystem*)gWindowSystem)->HandleError(ev);
	return 0;
}

void XWinSystem::HandleError(XErrorEvent *ev)
{
	char msg[80];

	XGetErrorText(display, ev->error_code, msg, 80);
	fprintf(stderr, "XWinSystem::HandleError: %s\n", msg);

	if (rpc)
		rpc->HandleError(ev);
}

void XWinSystem::Use()
{
}

long XWinSystem::GetCurrentServerTime()
{
	XEvent ev;
	XChangeProperty(display, awin, (int)XA_WM_NAME, XA_STRING, 8, PropModeAppend, (byte*) "", 0);
	if (XCheckTypedWindowEvent(display, awin, (int)PropertyChangeMask, &ev))
		return lasttime= ev.xproperty.time;
	return CurrentTime;
}

void XWinSystem::SetBusy(XWindow winid)
{
	if (cOpenLookWindowBusy.Exists() && winid != busy_winid) {
		int busy= 0;
		if (busy_winid)
			XChangeProperty(display, busy_winid, cOpenLookWindowBusy.XA(),
						XA_INTEGER, 32, PropModeReplace, (byte*) &busy, 1);
		busy_winid= winid;
		busy= 1;
		if (busy_winid)
			XChangeProperty(display, busy_winid, cOpenLookWindowBusy.XA(),
						XA_INTEGER, 32, PropModeReplace, (byte*) &busy, 1);
		XFlush(display);
	}
}

void XWinSystem::Allow()
{
	if (allow) {
		allow= FALSE;
		if (gWinDebug)
			fprintf(stderr, "*allow\n");
		XAllowEvents(display, SyncPointer, CurrentTime);
		XFlush(display);
	}
}

int xrestrict;

extern "C" {
	XBool rproc(Display*, XEvent *xe, char *)
	{
		switch (xrestrict) {
		case 0:
			return TRUE;
	
		case 1:
			return xe->type == PropertyNotify;
	
		default:
			break;
		}
		return TRUE;
	}
}

bool XWinSystem::Notify()
{
	Allow();
	if (XEventsQueued(display, QueuedAfterReading) > 0) {
		XEvent xe;
		Token t;
		XWindowPort *p;

		if (xrestrict > 0)
			XIfEvent(display, &xe, rproc, 0);
		else
			XNextEvent(display, &xe);
		
		if (xe.type == PropertyNotify) {
			//char *nm= XGetAtomName(display, xe.xproperty.atom);
			//fprintf(stderr, "%s\n", nm);
			//XFree(nm);
			if (rpc->HandleEvent(xe))
				return TRUE;
		}

		if (xe.xany.window == awin) {
			switch (xe.type) {
				
			case SelectionClear:
				if (xe.xselectionclear.selection == cClipboard.XA()) {
					//fprintf(stderr, "SelClear\n");
					gClipBoard->SetData(0);
					xholder= 0;
				}
				return TRUE;
		
			case SelectionRequest:
				SendClipBoard((XSelectionRequestEvent*) &xe);
				return TRUE;
			}
			return FALSE;
		}

		if (p= (XWindowPort*)FindWindowWithId((int)xe.xany.window)) {
			if (p->MapEvent(xe, &t))
				p->InputNotify(&t);
		}
		return TRUE;
	}
	SetBusy(0);
	Update();
	//Allow();
	XFlush(display);
	return FALSE;
}

WindowPort *XWinSystem::MakeWindow(Window *bw, WindowType wt, GrCursor curs)
{
	return new XWindowPort(bw, wt, curs);
}

VObject *XWinSystem::MakeWindowBorder(WindowType wt, VObject *in,
						char *title, bool block, bool fixed)
{
	return WindowSystem::MakeWindowBorder(wt, in, title, block, fixed);
}

XCursor XWinSystem::UseCursor(GrCursor c)
{
	if (CurMap[c] == 0) {
		unsigned int xc;
		
		switch (c) {
		case eCrsNone:          xc= XC_xterm; break;
		case eCrsArrow:         xc= XC_left_ptr; break;
		case eCrsBoldArrow:     xc= XC_left_ptr; break;
		case eCrsCross:         xc= XC_crosshair; break;
		case eCrsBoldCross:     xc= XC_cross; break;
		case eCrsUpArrow:       xc= XC_sb_up_arrow; break;
		case eCrsDownArrow:     xc= XC_sb_down_arrow; break;
		case eCrsLeftArrow:     xc= XC_sb_left_arrow; break;
		case eCrsRightArrow:    xc= XC_sb_right_arrow; break;
		case eCrsHourglass:     xc= XC_watch; break;
		case eCrsPen:           xc= XC_pencil; break;
		case eCrsMoveHand:      xc= XC_hand2; break;
		case eCrsHand:          xc= XC_hand2; break;
		case eCrsCrossHair:     xc= XC_crosshair; break;
		case eCrsMoveStretch:   xc= XC_hand2; break;
		case eCrsIBeam:         xc= XC_xterm; break;
		case eCrsLeftRightArrow:xc= XC_sb_h_double_arrow; break;
		case eCrsUpDownArrow:   xc= XC_sb_v_double_arrow; break;
		}
		CurMap[c]= XCreateFontCursor(display, xc);
	}
	return CurMap[c];
}

FontManager *XWinSystem::MakeFontManager()
{
	return new XFontManager;
}

DevBitmap *XWinSystem::MakeDevBitmap(Point sz, u_short *data,
				u_short depth, bool freedata, u_short pad)
{
	return new XBitmap(sz, data, depth, freedata, pad);
}

void XWinSystem::DevBell(long millisec)
{
	if (millisec != bellduration) {
		XKeyboardControl kb_ctrl;
		bellduration= millisec;
		kb_ctrl.bell_duration= (int) millisec;
		XChangeKeyboardControl(display, KBBellDuration, &kb_ctrl);
	}
	XBell(display, 0);
}

void XWinSystem::graphicDelay(u_int millisec)
{
	XSync(display, XFalse);
	gSystem->Wait(millisec);
}

IACImpl *XWinSystem::MakeRpcImpl(IAC *sc)
{
	rpc= new XIAC(sc, display, awin);
	return rpc;
}

char *XWinSystem::GetProperty(XWindow win, const Atom &atom, bool del, u_long *len)
{
	u_char *prop= 0;
	int result, actualFormat;
	unsigned long bytesAfter, numItems;
	Atom actualType;

	result= XGetWindowProperty(display, win, atom, 0, MAX_PROP_WORDS, del,
		XA_STRING, &actualType, &actualFormat, &numItems, &bytesAfter, &prop);

	if (actualType == None)
		return 0;

	if (result != Success || actualFormat != 8 || actualType != XA_STRING) {
		//fprintf(stderr, "GetProperty has bad format\n");
		if (prop)
			XFree((char*)prop);
		return 0;
	}

	if (len)
		*len= numItems;
	return (char*) prop;
}

int XWinSystem::DevFindWindow(const Point &)
{
	XWindow rw, cw= 0;
	int x, y;
	u_int s;

	if (XQueryPointer(display, rootwin, &rw, &cw, &x, &y, &x, &y, &s)) {
		if (cw == 0 || cw == rootwin)
			return 0;
		if (XQueryPointer(display, cw, &rw, &cw, &x, &y, &x, &y, &s))
			return cw;
	}
	return 0;
}

bool XWinSystem::DevIsEtWindow(int winid)
{
	if (winid == 0 || winid == rootwin)
		return FALSE;
	char *s= GetProperty(winid, cIsEtWindow.XA());
	if (s == 0)
		return FALSE;
	XFree(s);
	return TRUE;
}

//---- XDragDropService --------------------------------------------------------

int XDragDropService::DragDrop(int to, int code, int winid,
				const Point &globalpos, Rectangle *droprect)
{
	int rc= 0;

	(*os) << code SP << winid SP << globalpos;
	Talk(to, "dragdrop");
	(*is) >> rc >> *droprect;
	return rc;
}

int XDragDropService::Dispatch(int fr, const char *req, IStream &i, OStream &o)
{
	if (strcmp(req, "dragdrop") == 0) {
		Point globalpos;
		Rectangle r;
		int rc= 0, code= 0, winid= 0;

		i >> code >> winid >> globalpos;
		WindowPort *wp= gWindowSystem->FindWindowWithId(winid); // is local ?
		if (wp)
			rc= wp->DragDropNotify(code, globalpos, &r)+1;
		o << rc SP << r;
		return rc;
	}
	return Service::Dispatch(fr, req, i, o);
}

int XWinSystem::DevDragDrop(int code, int winid, const Point &globalpos,
							Rectangle *droprect)
{
	int rc= -1;

	char *s= GetProperty(winid, cIsEtWindow.XA());
	if (s) {
		int to= 0;
		if (sscanf(s, "%d", &to) == 1) {
			xrestrict= 0;
			rc= dragDropService->DragDrop(to, code, winid, globalpos, droprect);
			xrestrict= 0;
		}
		XFree(s);
	}
	return rc;
}

